package generate

import (
	"bytes"
	"encoding/json"
	"fmt"
	"net/http"
	"reflect"
	"regexp"
	"strconv"
	"strings"
	"unsafe"

	"github.com/zeromicro/go-zero/tools/goctl/api/spec"
	"github.com/zeromicro/go-zero/tools/goctl/plugin"
)

var strColon = []byte(":")

const (
	defaultOption   = "default"
	stringOption    = "string"
	optionalOption  = "optional"
	omitemptyOption = "omitempty"
	optionsOption   = "options"
	rangeOption     = "range"
	exampleOption   = "example"
	optionSeparator = "|"
	equalToken      = "="
	atRespDoc       = "@respdoc-"
)

func parseRangeOption(option string) (float64, float64, bool) {
	const str = "\\[([+-]?\\d+(\\.\\d+)?):([+-]?\\d+(\\.\\d+)?)\\]"
	result := regexp.MustCompile(str).FindStringSubmatch(option)
	if len(result) != 5 {
		return 0, 0, false
	}

	min, err := strconv.ParseFloat(result[1], 64)
	if err != nil {
		return 0, 0, false
	}

	max, err := strconv.ParseFloat(result[3], 64)
	if err != nil {
		return 0, 0, false
	}

	if max < min {
		return min, min, true
	}
	return min, max, true
}

func applyGenerate(p *plugin.Plugin) (*swaggerObject, error) {
	for _, group := range p.Api.Service.Groups {
		print(group.Annotation.Properties)
	}
	output := swaggerObject{
		Openapi: "3.1.0",
		Info: swaggerInfoObject{
			Title:       "Roc",
			Description: "",
			Version:     "1.0.0",
		},
		Paths: make(swaggerPathsObject),
	}

	// s.SecurityDefinitions = swaggerSecurityDefinitionsObject{}
	// newSecDefValue := swaggerSecuritySchemeObject{}
	// newSecDefValue.Name = "Authorization"
	// newSecDefValue.Description = "Enter JWT Bearer token **_only_**"
	// newSecDefValue.Type = "apiKey"
	// newSecDefValue.In = "header"
	// s.SecurityDefinitions["apiKey"] = newSecDefValue

	// s.Security = append(s.Security, swaggerSecurityRequirementObject{"apiKey": []string{}})

	requestResponseRefs := refMap{}
	renderServiceRoutes(p.Api.Service.Groups, output.Paths, requestResponseRefs)

	output.Components = renderTypeAsSchemas(p.Api.Types)
	tags := make([]swaggerTagObject, 0)
	tagsStr := make([]string, 0)
	for _, path := range output.Paths {
		if path.Get != nil {
			for _, tag := range path.Get.Tags {
				if !contains(tagsStr, tag) {
					tagsStr = append(tagsStr, tag)
					tags = append(tags, swaggerTagObject{
						Name: tag,
					})
				}

			}

		}
		if path.Delete != nil {
			for _, tag := range path.Delete.Tags {
				if !contains(tagsStr, tag) {
					tagsStr = append(tagsStr, tag)
					tags = append(tags, swaggerTagObject{
						Name: tag,
					})
				}
			}

		}
		if path.Post != nil {
			for _, tag := range path.Post.Tags {
				if !contains(tagsStr, tag) {
					tagsStr = append(tagsStr, tag)
					tags = append(tags, swaggerTagObject{
						Name: tag,
					})
				}
			}

		}
		if path.Put != nil {
			for _, tag := range path.Put.Tags {
				if !contains(tagsStr, tag) {
					tagsStr = append(tagsStr, tag)
					tags = append(tags, swaggerTagObject{
						Name: tag,
					})
				}
			}

		}
		if path.Patch != nil {
			for _, tag := range path.Patch.Tags {
				if !contains(tagsStr, tag) {
					tagsStr = append(tagsStr, tag)
					tags = append(tags, swaggerTagObject{
						Name: tag,
					})
				}
			}

		}
	}
	output.Tags = tags
	return &output, nil
}

func renderServiceRoutes(groups []spec.Group, paths swaggerPathsObject, requestResponseRefs refMap) {
	for _, group := range groups {
		for _, route := range group.Routes {
			path := group.GetAnnotation("prefix") + route.Path
			if path[0] != '/' {
				path = "/" + path
			}
			parameters := swaggerParametersObject{}
			var requestBody *swaggerRequestBodyObject

			if countParams(path) > 0 {
				p := strings.Split(path, "/")
				for i := range p {
					part := p[i]
					if strings.Contains(part, ":") {
						key := strings.TrimPrefix(p[i], ":")
						path = strings.Replace(path, fmt.Sprintf(":%s", key), fmt.Sprintf("{%s}", key), 1)

						spo := swaggerParameterObject{
							Name:     key,
							In:       "path",
							Required: true,
							Type:     "string",
						}

						// extend the comment functionality
						// to allow query string parameters definitions
						// EXAMPLE:
						// @doc(
						// 	summary: "Get Cart"
						// 	description: "returns a shopping cart if one exists"
						// 	customerId: "customer id"
						// )
						//
						// the format for a parameter is
						// paramName: "the param description"
						//

						prop := route.AtDoc.Properties[key]
						if prop != "" {
							// remove quotes
							spo.Description = strings.Trim(prop, "\"")
						}

						parameters = append(parameters, spo)
					}
				}
			}
			if defineStruct, ok := route.RequestType.(spec.DefineStruct); ok {
				for _, member := range defineStruct.Members {
					if member.Name == "" {
						memberDefineStruct, _ := member.Type.(spec.DefineStruct)
						for _, m := range memberDefineStruct.Members {
							if strings.Contains(m.Tag, "header") {
								tempKind := swaggerMapTypes[strings.Replace(m.Type.Name(), "[]", "", -1)]
								ftype, format, ok := primitiveSchema(tempKind, m.Type.Name())
								if !ok {
									ftype = tempKind.String()
									format = "UNKNOWN"
								}
								sp := swaggerParameterObject{In: "header", Type: ftype, Format: format}

								for _, tag := range m.Tags() {
									sp.Name = tag.Name
									if len(tag.Options) == 0 {
										sp.Required = true
										continue
									}

									required := true
									for _, option := range tag.Options {
										if strings.HasPrefix(option, optionsOption) {
											segs := strings.SplitN(option, equalToken, 2)
											if len(segs) == 2 {
												sp.Enum = strings.Split(segs[1], optionSeparator)
											}
										}

										if strings.HasPrefix(option, rangeOption) {
											segs := strings.SplitN(option, equalToken, 2)
											if len(segs) == 2 {
												min, max, ok := parseRangeOption(segs[1])
												if ok {
													sp.Schema.Minimum = min
													sp.Schema.Maximum = max
												}
											}
										}

										if strings.HasPrefix(option, defaultOption) {
											// segs := strings.Split(option, equalToken)
											// if len(segs) == 2 {
											//	sp.Default = segs[1]
											// }
										} else if strings.HasPrefix(option, optionalOption) || strings.HasPrefix(option, omitemptyOption) {
											required = false
										}

										if strings.HasPrefix(option, exampleOption) {
											segs := strings.Split(option, equalToken)
											if len(segs) == 2 {
												sp.Example = segs[1]
											}
										}
									}
									sp.Required = required
								}
								sp.Description = strings.TrimLeft(m.Comment, "//")
								parameters = append(parameters, sp)
							}
						}
						continue
					}
				}
				if strings.ToUpper(route.Method) == http.MethodGet {
					for _, member := range defineStruct.Members {
						if strings.Contains(member.Tag, "path") {
							continue
						}
						if embedStruct, isEmbed := member.Type.(spec.DefineStruct); isEmbed {
							for _, m := range embedStruct.Members {
								parameters = append(parameters, renderStruct(m))
							}
							continue
						}
						parameters = append(parameters, renderStruct(member))
					}
				} else {
					for _, member := range defineStruct.Members {
						if strings.Contains(member.Tag, "path") || strings.Contains(member.Tag, "json") {
							continue
						}
						if embedStruct, isEmbed := member.Type.(spec.DefineStruct); isEmbed {
							for _, m := range embedStruct.Members {
								parameters = append(parameters, renderStruct(m))
							}
							continue
						}
						parameters = append(parameters, renderStruct(member))
					}

					reqRef := fmt.Sprintf("#/components/schemas/%s", route.RequestType.Name())

					if len(route.RequestType.Name()) > 0 {
						schema := swaggerSchemaObject{
							schemaCore: schemaCore{
								Ref: reqRef,
							},
						}
						// parameter := swaggerParameterObject{
						//	Name:     "body",
						//	In:       "body",
						//	Required: true,
						//	Schema:   &schema,
						// }
						// doc := strings.Join(route.RequestType.Documents(), ",")
						// doc = strings.Replace(doc, "//", "", -1)
						//
						// if doc != "" {
						//	parameter.Description = doc
						// }
						//
						// parameters = append(parameters, parameter)
						requestBody = new(swaggerRequestBodyObject)
						requestBody = &swaggerRequestBodyObject{
							Content: make(map[string]swaggerMediaTypeObject),
						}
						requestBody.Content["application/json"] = swaggerMediaTypeObject{
							Schema: schema,
						}
					}
				}
			}

			pathItemObject, ok := paths[path]
			if !ok {
				pathItemObject = swaggerPathItemObject{}
			}

			desc := "A successful response."
			respSchema := schemaCore{}
			// respRef := swaggerSchemaObject{}
			if route.ResponseType != nil && len(route.ResponseType.Name()) > 0 {
				if strings.HasPrefix(route.ResponseType.Name(), "[]") {

					refTypeName := strings.Replace(route.ResponseType.Name(), "[", "", 1)
					refTypeName = strings.Replace(refTypeName, "]", "", 1)

					respSchema.Type = "array"
					respSchema.Items = &swaggerItemsObject{Ref: fmt.Sprintf("#/components/schemas/%s", refTypeName)}
				} else {
					respSchema.Ref = fmt.Sprintf("#/components/schemas/%s", route.ResponseType.Name())
				}
			}
			tags := strings.ReplaceAll(route.AtDoc.Properties["XApifoxFolder"], "\"", "")
			// if value := group.GetAnnotation("group"); len(value) > 0 {
			//	tags = value
			// }
			//
			// if value := group.GetAnnotation("swtags"); len(value) > 0 {
			//	tags = value
			// }

			operationObject := &swaggerOperationObject{
				Tags:       []string{tags},
				Parameters: parameters,
				Responses: swaggerResponsesObject{
					"200": swaggerResponseObject{
						Description: desc,
						Content: swaggerResponseContentObject{
							"application/json": swaggerMediaTypeObject{
								Schema: swaggerSchemaObject{
									schemaCore: respSchema,
								},
							},
						},
					},
				},
			}
			operationObject.RequestBody = requestBody

			for _, v := range route.Doc {
				markerIndex := strings.Index(v, atRespDoc)
				if markerIndex >= 0 {
					l := strings.Index(v, "(")
					r := strings.Index(v, ")")
					code := strings.TrimSpace(v[markerIndex+len(atRespDoc) : l])
					var comment string
					commentIndex := strings.Index(v, "//")
					if commentIndex > 0 {
						comment = strings.TrimSpace(strings.Trim(v[commentIndex+2:], "*/"))
					}
					content := strings.TrimSpace(v[l+1 : r])
					if strings.Index(v, ":") > 0 {
						lines := strings.Split(content, "\n")
						kv := make(map[string]string, len(lines))
						for _, line := range lines {
							sep := strings.Index(line, ":")
							key := strings.TrimSpace(line[:sep])
							value := strings.TrimSpace(line[sep+1:])
							kv[key] = value
						}
						kvByte, err := json.Marshal(kv)
						if err != nil {
							continue
						}
						operationObject.Responses[code] = swaggerResponseObject{
							Description: comment,
							Content: swaggerResponseContentObject{
								"application/json": swaggerMediaTypeObject{
									Schema: swaggerSchemaObject{
										schemaCore: schemaCore{
											Example: string(kvByte),
										},
									},
								},
							},
						}
					} else if len(content) > 0 {
						operationObject.Responses[code] = swaggerResponseObject{
							Description: comment,
							Content: swaggerResponseContentObject{
								"application/json": swaggerMediaTypeObject{
									Schema: swaggerSchemaObject{
										schemaCore: schemaCore{
											Ref: fmt.Sprintf("#/components/schemas/%s", content),
										},
									},
								},
							},
						}
					}
				}
			}

			// set OperationID
			operationObject.OperationID = route.Handler

			for _, param := range operationObject.Parameters {
				if param.Schema != nil && param.Schema.Ref != "" {
					requestResponseRefs[param.Schema.Ref] = struct{}{}
				}
			}
			operationObject.Summary = strings.ReplaceAll(route.AtDoc.Properties["Summary"], "\"", "")
			operationObject.XApifoxFolder = strings.ReplaceAll(route.AtDoc.Properties["XApifoxFolder"], "\"", "")
			operationObject.XApifoxStatus = strings.ReplaceAll(route.AtDoc.Properties["XApifoxStatus"], "\"", "")

			if len(route.AtDoc.Properties) > 0 {
				operationObject.Description, _ = strconv.Unquote(route.AtDoc.Properties["Description"])
			}

			operationObject.Description = strings.ReplaceAll(operationObject.Description, "\"", "")

			// if group.Annotation.Properties["jwt"] != "" {
			//	operationObject.Security = &[]swaggerSecurityRequirementObject{{"apiKey": []string{}}}
			// }

			switch strings.ToUpper(route.Method) {
			case http.MethodGet:
				pathItemObject.Get = operationObject
			case http.MethodPost:
				pathItemObject.Post = operationObject
			case http.MethodDelete:
				pathItemObject.Delete = operationObject
			case http.MethodPut:
				pathItemObject.Put = operationObject
			case http.MethodPatch:
				pathItemObject.Patch = operationObject
			}

			paths[path] = pathItemObject
		}
	}
}

func renderStruct(member spec.Member) swaggerParameterObject {
	tempKind := swaggerMapTypes[strings.Replace(member.Type.Name(), "[]", "", -1)]

	ftype, format, ok := primitiveSchema(tempKind, member.Type.Name())
	if !ok {
		ftype = tempKind.String()
		format = "UNKNOWN"
	}
	_ = format
	sp := swaggerParameterObject{In: "query", Schema: &swaggerSchemaObject{
		schemaCore: schemaCore{Type: ftype},
	}}

	for _, tag := range member.Tags() {
		sp.Name = tag.Name
		if len(tag.Options) == 0 {
			sp.Required = true
			continue
		}

		required := true
		for _, option := range tag.Options {
			if strings.HasPrefix(option, optionsOption) {
				segs := strings.SplitN(option, equalToken, 2)
				if len(segs) == 2 {
					sp.Enum = strings.Split(segs[1], optionSeparator)
				}
			}

			if strings.HasPrefix(option, rangeOption) {
				segs := strings.SplitN(option, equalToken, 2)
				if len(segs) == 2 {
					min, max, ok := parseRangeOption(segs[1])
					if ok {
						sp.Schema.Minimum = min
						sp.Schema.Maximum = max
					}
				}
			}

			if strings.HasPrefix(option, defaultOption) {
				segs := strings.Split(option, equalToken)
				if len(segs) == 2 {
					// sp.Default = segs[1]
				}
			} else if strings.HasPrefix(option, optionalOption) || strings.HasPrefix(option, omitemptyOption) {
				required = false
			}

			if strings.HasPrefix(option, exampleOption) {
				segs := strings.Split(option, equalToken)
				if len(segs) == 2 {
					sp.Example = segs[1]
				}
			}
		}
		sp.Required = required
	}

	if len(member.Comment) > 0 {
		sp.Description = strings.TrimLeft(member.Comment, "//")
	}

	return sp
}

func renderTypeAsSchemas(types []spec.Type) swaggerComponentsObject {
	schemas := make(map[string]swaggerSchemaObject, 0)
	for _, t := range types {
		schema := swaggerSchemaObject{
			schemaCore: schemaCore{
				Type: "object",
			},
		}
		defineStruct, _ := t.(spec.DefineStruct)
		schema.Title = defineStruct.Name()

		if strings.Contains(t.Name(), "Resp") {
			if schema.Properties == nil {
				schema.Properties = &swaggerSchemaObjectProperties{}
			}
			*schema.Properties = append(*schema.Properties, keyVal{
				Key: "code",
				Value: swaggerSchemaObject{
					schemaCore: schemaCore{
						Type:   "integer",
						Format: "int64",
					},
				},
			})
			*schema.Properties = append(*schema.Properties, keyVal{
				Key: "msg",
				Value: swaggerSchemaObject{
					schemaCore: schemaCore{
						Type: "string",
					},
				},
			})
			schema.Required = append(schema.Required, "code", "msg")

			dataSchema := swaggerSchemaObject{
				schemaCore: schemaCore{
					Type: "object",
				},
			}

			for _, member := range defineStruct.Members {
				if hasPathParameters(member) {
					continue
				}
				kv := keyVal{Value: schemaOfField(member)}
				kv.Key = member.Name
				if tag, err := member.GetPropertyName(); err == nil {
					kv.Key = tag
				}
				if kv.Key == "" {
					memberStruct, _ := member.Type.(spec.DefineStruct)
					for _, m := range memberStruct.Members {

						if strings.Contains(m.Tag, "header") {
							continue
						}

						mkv := keyVal{
							Value: schemaOfField(m),
							Key:   m.Name,
						}

						if tag, err := m.GetPropertyName(); err == nil {
							mkv.Key = tag
						}
						if dataSchema.Properties == nil {
							dataSchema.Properties = &swaggerSchemaObjectProperties{}
						}
						*dataSchema.Properties = append(*dataSchema.Properties, mkv)
					}
					continue
				}
				if dataSchema.Properties == nil {
					dataSchema.Properties = &swaggerSchemaObjectProperties{}
				}
				*dataSchema.Properties = append(*dataSchema.Properties, kv)

				for _, tag := range member.Tags() {
					if len(tag.Options) == 0 {
						if !contains(dataSchema.Required, tag.Name) && tag.Name != "required" {
							dataSchema.Required = append(dataSchema.Required, tag.Name)
						}
						continue
					}

					required := true
					for _, option := range tag.Options {
						// case strings.HasPrefix(option, defaultOption):
						// case strings.HasPrefix(option, optionsOption):

						if strings.HasPrefix(option, optionalOption) || strings.HasPrefix(option, omitemptyOption) {
							required = false
						}
					}

					if required && !contains(dataSchema.Required, tag.Name) {
						dataSchema.Required = append(dataSchema.Required, tag.Name)
					}
				}
			}
			if len(defineStruct.Members) > 0 {
				*schema.Properties = append(*schema.Properties, keyVal{
					Key:   "data",
					Value: dataSchema,
				})
				schema.Required = append(schema.Required, "data")
			}
		} else {
			for _, member := range defineStruct.Members {
				if hasPathParameters(member) || hasFormParameters(member) {
					continue
				}
				kv := keyVal{Value: schemaOfField(member)}
				kv.Key = member.Name
				if tag, err := member.GetPropertyName(); err == nil {
					kv.Key = tag
				}
				if kv.Key == "" {
					memberStruct, _ := member.Type.(spec.DefineStruct)
					for _, m := range memberStruct.Members {

						if strings.Contains(m.Tag, "header") {
							continue
						}

						mkv := keyVal{
							Value: schemaOfField(m),
							Key:   m.Name,
						}

						if tag, err := m.GetPropertyName(); err == nil {
							mkv.Key = tag
						}
						if schema.Properties == nil {
							schema.Properties = &swaggerSchemaObjectProperties{}
						}
						*schema.Properties = append(*schema.Properties, mkv)
					}
					continue
				}
				if schema.Properties == nil {
					schema.Properties = &swaggerSchemaObjectProperties{}
				}
				*schema.Properties = append(*schema.Properties, kv)

				for _, tag := range member.Tags() {
					if len(tag.Options) == 0 {
						if !contains(schema.Required, tag.Name) && tag.Name != "required" {
							schema.Required = append(schema.Required, tag.Name)
						}
						continue
					}

					required := true
					for _, option := range tag.Options {
						// case strings.HasPrefix(option, defaultOption):
						// case strings.HasPrefix(option, optionsOption):

						if strings.HasPrefix(option, optionalOption) || strings.HasPrefix(option, omitemptyOption) {
							required = false
						}
					}

					if required && !contains(schema.Required, tag.Name) {
						schema.Required = append(schema.Required, tag.Name)
					}
				}
			}
		}

		schemas[t.Name()] = schema
	}
	output := swaggerComponentsObject{
		Schemas: schemas,
	}
	return output
}

func hasPathParameters(member spec.Member) bool {
	for _, tag := range member.Tags() {
		if tag.Key == "path" {
			return true
		}
	}

	return false
}

func hasJsonParameters(member spec.Member) bool {
	for _, tag := range member.Tags() {
		if tag.Key == "json" {
			return true
		}
	}

	return false
}

func hasFormParameters(member spec.Member) bool {
	for _, tag := range member.Tags() {
		if tag.Key == "form" {
			return true
		}
	}

	return false
}

func schemaOfField(member spec.Member) swaggerSchemaObject {
	ret := swaggerSchemaObject{}

	var core schemaCore

	kind := swaggerMapTypes[member.Type.Name()]
	var props *swaggerSchemaObjectProperties

	comment := member.GetComment()
	comment = strings.Replace(comment, "//", "", -1)
	comment = strings.TrimSpace(comment)

	switch ft := kind; ft {
	case reflect.Invalid: // []Struct 也有可能是 Struct
		// []Struct
		// map[ArrayType:map[Star:map[StringExpr:UserSearchReq] StringExpr:*UserSearchReq] StringExpr:[]*UserSearchReq]
		refTypeName := strings.Replace(member.Type.Name(), "[", "", 1)
		refTypeName = strings.Replace(refTypeName, "]", "", 1)
		refTypeName = strings.Replace(refTypeName, "*", "", 1)
		refTypeName = strings.Replace(refTypeName, "{", "", 1)
		refTypeName = strings.Replace(refTypeName, "}", "", 1)
		// interface

		if refTypeName == "interface" {
			core = schemaCore{Type: "object"}
		} else if refTypeName == "mapstringstring" {
			core = schemaCore{Type: "object"}
		} else if strings.HasPrefix(refTypeName, "[]") {
			core = schemaCore{Type: "array"}

			tempKind := swaggerMapTypes[strings.Replace(refTypeName, "[]", "", -1)]
			ftype, format, ok := primitiveSchema(tempKind, refTypeName)
			if ok {
				core.Items = &swaggerItemsObject{Type: ftype, Format: format}
			} else {
				core.Items = &swaggerItemsObject{Type: ft.String(), Format: "UNKNOWN"}
			}

		} else {
			core = schemaCore{
				Ref: "#/components/schemas/" + refTypeName,
			}
		}
	case reflect.Slice:
		tempKind := swaggerMapTypes[strings.Replace(member.Type.Name(), "[]", "", -1)]
		ftype, format, ok := primitiveSchema(tempKind, member.Type.Name())

		if ok {
			core = schemaCore{Type: ftype, Format: format}
		} else {
			core = schemaCore{Type: ft.String(), Format: "UNKNOWN"}
		}
	default:
		ftype, format, ok := primitiveSchema(ft, member.Type.Name())
		if ok {
			core = schemaCore{Type: ftype, Format: format}
		} else {
			core = schemaCore{Type: ft.String(), Format: "UNKNOWN"}
		}
	}

	switch ft := kind; ft {
	case reflect.Slice:
		ret = swaggerSchemaObject{
			schemaCore: schemaCore{
				Type:  "array",
				Items: (*swaggerItemsObject)(&core),
			},
		}
	case reflect.Invalid:
		// 判断是否数组
		if strings.HasPrefix(member.Type.Name(), "[]") {
			ret = swaggerSchemaObject{
				schemaCore: schemaCore{
					Type:  "array",
					Items: (*swaggerItemsObject)(&core),
				},
			}
		} else {
			ret = swaggerSchemaObject{
				schemaCore: core,
				Properties: props,
			}
		}
		if strings.HasPrefix(member.Type.Name(), "map") {
			fmt.Println("暂不支持map类型")
		}
	default:
		ret = swaggerSchemaObject{
			schemaCore: core,
			Properties: props,
		}
	}

	// 判断字段是否为废弃字段
	if strings.Contains(strings.ToLower(comment), "deprecated") {
		ret.Deprecated = true
		comment = strings.Replace(comment, "deprecated", "", 1)
		comment = strings.Replace(comment, "Deprecated", "", 1)
		comment = strings.Replace(comment, "DEPRECATED", "", 1)
		comment = strings.Trim(comment, ",")
		comment = strings.Trim(comment, "，")
		comment = strings.Trim(comment, ":")
		comment = strings.Trim(comment, "：")
		comment = strings.TrimSpace(comment)
	}
	ret.Description = comment

	for _, tag := range member.Tags() {
		if len(tag.Options) == 0 {
			continue
		}
		for _, option := range tag.Options {
			switch {
			case strings.HasPrefix(option, defaultOption):
				segs := strings.Split(option, equalToken)
				if len(segs) == 2 {
					ret.Default = segs[1]
				}
			case strings.HasPrefix(option, optionsOption):
				segs := strings.SplitN(option, equalToken, 2)
				if len(segs) == 2 {
					ret.XApifox = new(xApifox)
					ret.XApifox.EnumDescriptions = make(map[string]string)
					for _, op := range strings.Split(segs[1], optionSeparator) {
						if !strings.Contains(op, "(") {
							ret.Enum = append(ret.Enum, op)
						} else {
							str := op
							re := regexp.MustCompile(`\((.*?)\)`)
							match := re.FindStringSubmatch(str)
							eStr := ""
							eStrDesc := ""
							if len(match) > 1 {
								eStr = strings.Split(str, "(")[0]
								eStrDesc = match[1]
							}
							ret.Enum = append(ret.Enum, eStr)
							ret.XApifox.EnumDescriptions[eStr] = eStrDesc
						}
					}
				}
			case strings.HasPrefix(option, rangeOption):
				segs := strings.SplitN(option, equalToken, 2)
				if len(segs) == 2 {
					min, max, ok := parseRangeOption(segs[1])
					if ok {
						ret.Minimum = min
						ret.Maximum = max
					}
				}
			case strings.HasPrefix(option, exampleOption):
				segs := strings.Split(option, equalToken)
				if len(segs) == 2 {
					ret.Example = segs[1]
				}
			}
		}
	}

	return ret
}

// https://swagger.io/specification/ Data Types
func primitiveSchema(kind reflect.Kind, t string) (ftype, format string, ok bool) {
	switch kind {
	case reflect.Int:
		return "integer", "int32", true
	case reflect.Uint:
		return "integer", "uint32", true
	case reflect.Int8:
		return "integer", "int8", true
	case reflect.Uint8:
		return "integer", "uint8", true
	case reflect.Int16:
		return "integer", "int16", true
	case reflect.Uint16:
		return "integer", "uin16", true
	case reflect.Int64:
		return "integer", "int64", true
	case reflect.Uint64:
		return "integer", "uint64", true
	case reflect.Bool:
		return "boolean", "boolean", true
	case reflect.String:
		return "string", "", true
	case reflect.Float32:
		return "number", "float", true
	case reflect.Float64:
		return "number", "double", true
	case reflect.Slice:
		return strings.Replace(t, "[]", "", -1), "", true
	default:
		return "", "", false
	}
}

// StringToBytes converts string to byte slice without a memory allocation.
func stringToBytes(s string) (b []byte) {
	return *(*[]byte)(unsafe.Pointer(
		&struct {
			string
			Cap int
		}{s, len(s)},
	))
}

func countParams(path string) uint16 {
	var n uint16
	s := stringToBytes(path)
	n += uint16(bytes.Count(s, strColon))
	return n
}

func contains(s []string, str string) bool {
	for _, v := range s {
		if v == str {
			return true
		}
	}

	return false
}
